package deepr.src.pg;

import deepr.anno.SchemaType;
import deepr.behaviour.RequestDispose;
import deepr.core.DisposeContext;
import deepr.core.PropertyData;
import deepr.core.SchemaData;
import deepr.core.SchemaManager;
import deepr.db.PgMapper;
import deepr.entity.DeeprInfoResponse;
import deepr.entity.DeeprRequest;
import deepr.entity.DeeprListResponse;
import deepr.exception.DeeprAssert;
import deepr.exception.DeeprException;
import deepr.lib.Map2Bean;
import deepr.lib.PgQlLib;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Record;
import org.neo4j.driver.internal.value.MapValue;
import org.springframework.stereotype.Component;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
@Data
public class PgDispose implements RequestDispose {

  private PgDisposeContext ctx;
  private Map<String, MatchForeignData> matchForeignInfo = new HashMap<>();
  private int pageSize = 0;
  private List<Map<String, Object>> rows;

  public <T> DeeprListResponse<T> list(DisposeContext ctx, DeeprRequest deeprRequest) throws Exception {
    this.ctx = (PgDisposeContext) ctx;
    String str = PgQlLib.list(ctx, deeprRequest);
    log.debug("str: {}\n, params: {}", str, ctx.getFilterParams());
    rows = ((PgMapper) ctx.getDeeprMapper()).queryList(str, ctx.getFilterParams());
    pageSize = rows.size();
    log.debug("result: {}", rows);
    getOptionalMap(deeprRequest.getOptional());
    getOptionalMap(deeprRequest.getMatch());
    List<T> list2 = setForeign(ctx, rows);
    DeeprListResponse<T> response = new DeeprListResponse<>();
    response.setList(list2);
    if (rows.size() > 0) {
      response.setTotalCount((Long) rows.get(0).getOrDefault("totalcount", 0), deeprRequest.getPageSize());
    }
    response.setObject(deeprRequest.getObject());
    return response;
  }


  public <T> DeeprInfoResponse<T> info(DisposeContext ctx, DeeprRequest deeprRequest) throws Exception {
    this.ctx = (PgDisposeContext) ctx;
    String str = PgQlLib.info(ctx, deeprRequest);
    log.debug("str: {}\n, params: {}", str, ctx.getFilterParams());
    Map<String, Object> row = ((PgMapper) ctx.getDeeprMapper()).queryInfo(str, ctx.getFilterParams());
    DeeprInfoResponse<T> response = new DeeprInfoResponse<>();
    response.setObject(deeprRequest.getObject());
    if (row == null) {
      return response;
    }
    pageSize = 2;
    rows = new LinkedList<>();
    rows.add(row);
    getOptionalMap(deeprRequest.getOptional());
    getOptionalMap(deeprRequest.getMatch());
    List<T> list2 = setForeign(ctx, rows);
    response.setInfo(list2.get(0));
    return response;
  }

  // {"people": ["id", "name"]}
  public void getOptionalMap(Map<String, String[]> optional) throws DeeprException, NoSuchFieldException {
    SchemaData schemaData = ctx.getSchemaData();
    if (optional.size() == 0)
      return;
    for (Map.Entry<String, String[]> entry : optional.entrySet()) {
      String key = entry.getKey();
      String[] select = entry.getValue();
      PropertyData propertyData = schemaData.getPropertyData(key);
      String forProperty = propertyData.getForProperty();
      Class<?> czz = propertyData.getSchema();
      DeeprAssert.isTrue(czz != null, key + " for " + schemaData.getName() + " not exists");
      SchemaData foreignSchema = SchemaManager.getSchemaData(czz);
      Class<?> foreignCzz = foreignSchema.getSchema();
      String primaryKey = foreignSchema.getPrimaryKey();
      MatchForeignData m = new MatchForeignData(key, foreignCzz, primaryKey, select, pageSize);
      PropertyData forPropertyData = schemaData.getPropertyData(forProperty);
      for (Map<String, Object> row : rows) {
        m.setArrayNth(row.get(forPropertyData.getName()));
      }
      matchForeignInfo.put(forProperty, m);
    }
  }


  public <T> List<T> setForeign(DisposeContext ctx, List<Map<String, Object>> list) throws Exception {
    List<T> results = new LinkedList<>();
    SchemaData schemaData = ctx.getSchemaData();
    Class<T> czz = schemaData.getSchema();
    if (matchForeignInfo.size() == 0) {
      for (Map<String, Object> ele : list) {
        T v = Map2Bean.mapToBean(ele, czz);
        results.add(v);
      }
      return results;
    }
    for (Map.Entry<String, MatchForeignData> entry : matchForeignInfo.entrySet()) {
      MatchForeignData m = entry.getValue();
      m.queryDb(ctx);
      String forProperty = entry.getKey();
      String dbName = schemaData.getPropertyData(forProperty).getName();
      String foreignObject = m.getObject();
      SchemaType schemaType = m.getSchemaType();
      String foreignPrimaryKey = m.getPrimaryKey();
      Class<?> foreignCzz = m.getSchemaCzz();
      List<Map<String, Object>> result = m.getResult();
      PropertyDescriptor descriptor = new PropertyDescriptor(foreignObject, czz);
      Method setMethod = descriptor.getWriteMethod();
//      Method setMethod = czz.getMethod("set" + NameLib.captureName(foreignObject), foreignCzz);
      for (Map<String, Object> ele : list) {
        Object forPropertyValue = ele.get(dbName);
        T v = Map2Bean.mapToBean(ele, czz);
        switch (schemaType) {
          case PG:
            for (Map<String, Object> rs : result) {
              if (rs.get(foreignPrimaryKey).equals(forPropertyValue)) {
                Object foreignObj = Map2Bean.mapToBean(rs, foreignCzz);
                setMethod.invoke(v, foreignObj);
                break;
              }
            }
            break;
          case NEO4J:
            List<Record> graphResult = m.getGraphResult();
            for (Record record : graphResult) {
              MapValue mapValue = (MapValue) record.get(foreignObject);
              PropertyData propertyData = schemaData.getPropertyData(foreignPrimaryKey);
              Object v1 = Map2Bean.convertType(foreignPrimaryKey, mapValue.get(foreignPrimaryKey), propertyData.getType());
              if (v1 != null && v1.equals(forPropertyValue)) {
                Object foreignObj = Map2Bean.mapToBean(mapValue, foreignCzz);
                setMethod.invoke(v, foreignObj);
                break;
              }
            }
            break;
          default:
            break;
        }
        results.add(v);
      }
    }
    return results;
  }


}
